package se.rende.schema2ical.client;

import se.rende.schema2ical.client.JsonParser.MapPair;
import se.rende.schema2ical.client.JsonParser.MapParser;


public class JsonParser {
	private final String s;
	int pos;

	public JsonParser(String s) {
		this.s = s;
		this.pos = 0;
	}
	
	public JsonParser(String s, int startPos) {
		this.s = s;
		this.pos = startPos;
	}
	
	/**
	 * Returns a String, an Integer, a Double, an ArrayParser or a MapParser, depending on the supplied json content.
	 * 
	 * @return
	 */
	public Object get() {
		skipSpace();
		if (s.charAt(pos) == '[') {
			return new ArrayParser(this);
		} else if (s.charAt(pos) == '{') {
			return new MapParser(this);
		} else if (isNumberChar(s.charAt(pos))) {
			return getNumber();
		} else if (isStringStartChar(s.charAt(pos))) {
			return getString();
		} else {
			return null;
		}
	}

	private String getString() {
		char delim = s.charAt(pos++);
		StringBuilder sb = new StringBuilder();
		while (s.charAt(pos) != delim) {
			sb.append(s.charAt(pos++));
		}
		pos++;
		return sb.toString();
	}

	private Object getNumber() {
		StringBuilder sb = new StringBuilder();
		while (pos < s.length() && isNumberChar(s.charAt(pos))) {
			sb.append(s.charAt(pos++));
		}
		return new Integer(sb.toString());
	}
	
	private String getMapPairName() {
		StringBuilder sb = new StringBuilder();
		while (!(isSpaceChar(s.charAt(pos)) || s.charAt(pos) == ':')) {
			sb.append(s.charAt(pos++));
		}
		return sb.toString();
	}

	void skipSpace() {
		while (isSpaceChar(s.charAt(pos))) {
			pos++;
		}
	}

	private boolean isSpaceChar(char ch) {
		return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
	}

	private boolean isNumberChar(char ch) {
		return ch == '-' || ch == '.' || ( '0' <= ch && ch <= '9');
	}

	private boolean isStringStartChar(char ch) {
		return ch == '\'' || ch == '"';
	}

	public class JsonSyntaxError extends Error {
		private static final long serialVersionUID = 4812550115711614769L;
		
		public JsonSyntaxError(String message) {
			super(message);
		}
	}
	
	public class ArrayParser {
		private final JsonParser parser;
		boolean endOfArray = false;
		
		public ArrayParser(JsonParser parser) {
			this.parser = parser;
			parser.pos++;
		}
		
		public boolean hasNext() {
			if (endOfArray) {
				return false;
			}
			parser.skipSpace();
			if (pos < s.length()) {
				if (s.charAt(pos) == ']') {
					pos++;
					endOfArray = true;
					return false;
				} else {
					return true;
				}
			} else {
				endOfArray = true;
				return false;
			}
		}
		
		public Object get() {
			if (endOfArray) {
				return null;
			}
			parser.skipSpace();
			if (pos < s.length() && s.charAt(pos) == ',') {
				pos++;
			} else if (pos < s.length() && s.charAt(pos) == ']') {
				endOfArray = true;
				pos++;
				return null;
			} else if (pos == s.length()) {
				throw new JsonSyntaxError("End of string while expecting an array elements");
			}
			return parser.get();
		}
	}
	
	public class MapPair {
		public String name;
		public Object value;
		
		public MapPair(String name, Object value) {
			this.name = name;
			this.value = value;
		}
	}

	public class MapParser {
		private final JsonParser parser;
		boolean endOfMap = false;

		public MapParser(JsonParser parser) {
			this.parser = parser;
			parser.pos++;
		}

		public boolean hasNext() {
			if (endOfMap) {
				return false;
			}
			parser.skipSpace();
//			System.out.println("JsonParser.MapParser.hasNext() " + s.substring(pos));
			if (pos < s.length()) {
				if (s.charAt(pos) == '}') {
					pos++;
					endOfMap = true;
					return false;
				} else {
					return true;
				}
			} else {
				endOfMap = true;
				return false;
			}
		}

		public MapPair getMapPair() {
			if (endOfMap) {
				return null;
			}
			parser.skipSpace();
			if (pos < s.length() && s.charAt(pos) == ',') {
				pos++;
			} else if (pos < s.length() && s.charAt(pos) == '}') {
				endOfMap = true;
				pos++;
				return null;
			}
			skipSpace();
			String name = getMapPairName();
			skipSpace();
			if (s.charAt(pos) != ':') {
				throw new JsonSyntaxError("expected : in map, but got '" + s.charAt(pos) + "'");
			}
			pos++;
			Object value = parser.get();
			return new MapPair(name, value);
		}

		
	}

	public MapParser getMapParser() {
		return (MapParser) get();
	}

}
